package com.hyhy.hycloud.uaa.service;

import com.hyhy.hycloud.uaa.AppIdProperties;
import com.hyhy.hycloud.uaa.UaaErrorType;
import com.hyhy.hycloud.uaa.VerificationCodeProperties;
import com.hyhy.hycloud.uaa.dto.UserInfoResult;
import com.hyhy.hycloud.uaa.model.*;
import com.hyhy.hycloud.uaa.repository.*;
import com.hyhy.hycloud.uaa.security.SecurityUser;
import com.hyhy.hycloud.uaa.security.crypto.Md5PasswordEncoder;
import com.hyhy.hycloud.uaa.security.exception.*;
import com.hyhy.hycloud.ucenter.rpc.api.UserServiceApi;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.io.UnsupportedEncodingException;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 用户应用服务
 *
 * @author Xuegui Yuan
 * @date 2018-09-18
 */
@Service
public class UserService implements UserDetailsByUsernameFinder, UserDetailsByMobileFinder, UserRegister,
        AppIdConfigFinder {

    @Autowired
    UserRepository userRepository;

    @Autowired
    RoleRepository roleRepository;

    @Autowired
    UserRoleRepository userRoleRepository;

    @Autowired
    OrgRepository orgRepository;

    @Autowired
    RoleResourceRepository roleResourceRepository;

    @Autowired
    ResourceRepository resourceRepository;

    @Autowired
    AppIdProperties appIdProperties;

    @Autowired
    VerificationCodeProperties verificationCodeProperties;

    @Autowired
    LoginAttemptService loginAttemptService;

    @Autowired
    private UserServiceApi userServiceApi;

    @Value("${fileUrlPrefix}")
    private String fileUrlPrefix;

    private PasswordEncoder passwordEncoder = new Md5PasswordEncoder();

    public UserInfoResult<?> getUserInfoBySecurityUser(String appId, SecurityUser securityUser, boolean withResources) {
        Assert.hasText(appId, "'appId' must not be empty");
        if (!securityUser.isActive()) {
            throw new UserDisabledException();
        }
        Optional<User> optionalUser = userRepository.findById(securityUser.getId());
        User user = optionalUser.orElseThrow(UaaErrorType.USER_NOT_FOUND::toRuntimeException);
        UserInfoResult<Object> userInfoResult = new UserInfoResult<>(securityUser);
        user.setPhoto(fileUrlPrefix + user.getPhoto());
        if (!StringUtils.isEmpty(user.getOrgId())) {
            Org org = orgRepository.findById(user.getOrgId()).orElse(null);
            if (org != null) {
                user.setOrgName(org.getOrgName());
            }
        }
        userInfoResult.setDetails(user);
        List<Role> roleList = findRolesByUserId(user.getId());
        fillRoleCodeListAndResourceList(appId, userInfoResult, roleList, withResources);
        return userInfoResult;
    }

    public UserInfoResult getUserInfoByUserId(String appId, String userId, boolean withResources) {
        SecurityUser securityUser = (SecurityUser) findUserDetailsByUserId(userId);
        return getUserInfoBySecurityUser(appId, securityUser, withResources);
    }

    public void checkUserExistByMobile(String appId, String mobile, String bizCode) {
        if (this.verificationCodeProperties.getSendStrategy().getCheckUserExistBizCodes().contains(bizCode)
                && !this.appIdProperties.getAutoRegisterApps().contains(appId)) {
            if (!userRepository.existsByMobile(mobile)) {
                throw new UserNotFoundException();
            }
        }
    }

    @Override
    public UserDetails findUserDetailsByMobile(String appId, String mobile)
            throws LoginFailureLimitException, UserNotFoundException, UserDisabledException {
        if (loginAttemptService.isLoginFailLimit(mobile)) {
            throw new LoginFailureLimitException();
        }
        Optional<User> userOptional = userRepository.findByMobile(mobile);
        User user = userOptional.orElseThrow(UserNotFoundException::new);
        return getUserDetail(appId, user);
    }

    @Override
    public UserDetails findUserDetailsByUsername(String appId, String username)
            throws LoginFailureLimitException, UserNotFoundException, UserDisabledException {
        if (loginAttemptService.isLoginFailLimit(username)) {
            throw new LoginFailureLimitException();
        }
        Optional<User> userOptional = userRepository.findByUsername(username);
        User user = userOptional.orElseThrow(UserNotFoundException::new);
        return getUserDetail(appId, user);
    }

    public UserDetails findUserDetailsByUserId(String userId) throws UserNotFoundException, UserDisabledException {
        Optional<User> userOptional = userRepository.findById(userId);
        User user = userOptional.orElseThrow(UserNotFoundException::new);
        checkUserStatus(user);
        return buildUserDetailsByUser(user);
    }

    /**
     * 根据旧密码修改登录密码
     *
     * @param userId      用户ID
     * @param oldPassword 旧密码
     * @param newPassword 新密码
     * @throws UserNotFoundException        用户不存在
     * @throws UnsupportedEncodingException 不支持的编码异常
     */
    public void updateLoginPassword(String userId, String oldPassword, String newPassword) throws UserNotFoundException,
            UnsupportedEncodingException {
        Assert.notNull(userId, "userId must be not null");
        Assert.notNull(oldPassword, "oldPassword must be not null");
        Assert.notNull(newPassword, "newPassword must be not null");

        Optional<User> optionalUser = userRepository.findById(userId);
        User user = optionalUser.orElseThrow(() -> new UserNotFoundException(UaaErrorType.USER_NOT_FOUND.getMessage()));
        checkUserStatus(user);
        String oldPwd = new String(Base64.getDecoder().decode(oldPassword), "UTF-8");
        String newPwd = new String(Base64.getDecoder().decode(newPassword), "UTF-8");
        if (passwordEncoder.matches(oldPwd, user.getPassword())) {
            if (oldPwd.equals(newPwd)) {
                throw UaaErrorType.OLD_NEW_PASSWORD_EQUALS.toRuntimeException();
            }
            user.setPassword(passwordEncoder.encode(newPwd));
            userServiceApi.modifyUserPassword(user.getId(), user.getPassword());
//            userRepository.save(user);
        } else {
            throw UaaErrorType.BAD_PASSWORD_ERROR.toRuntimeException();
        }
    }

    /**
     * 修改登录密码
     *
     * @param user        用户
     * @param newPassword 新密码
     * @throws UnsupportedEncodingException 不支持的编码异常
     */
    public void updateLoginPassword(User user, String newPassword) throws UnsupportedEncodingException {
        Assert.notNull(user, "userId must be not null");
        Assert.notNull(newPassword, "newPassword must be not null");
        checkUserStatus(user);
        String pwd = new String(Base64.getDecoder().decode(newPassword), "UTF-8");
        user.setPassword(passwordEncoder.encode(pwd));
        userServiceApi.modifyUserPassword(user.getId(), user.getPassword());
//        userRepository.save(user);
    }

    @Override
    public UserDetails registerUser(String appId, String mobile) {
        User user = new User();
        user.setUsername(mobile);
        user.setMobile(mobile);
        user.setStatus(0);
        return buildUserDetailsByUser(userRepository.save(user));
    }

    @Override
    public List<String> supportUsernamePasswordList() {
        return this.appIdProperties.getUsernamePasswordApps();
    }

    @Override
    public List<String> supportMobilePasswordList() {
        return this.appIdProperties.getMobilePasswordApps();
    }

    @Override
    public List<String> supportSmsVerificationCodeList() {
        return this.appIdProperties.getSmsVerificationCodeApps();
    }

    @Override
    public List<String> autoRegisterList() {
        return this.appIdProperties.getAutoRegisterApps();
    }

    public List<String> getAdminApps() {
        return this.appIdProperties.getAdminApps();
    }

    private UserDetails getUserDetail(String appId, User user) {
        checkUserStatus(user);
        return buildUserDetailsByUser(user);
    }

    /**
     * 功能描述: 判断用户账号状态，生效时间、失效时间，所属机构状态
     * @auther: WUXIN
     */
    private void checkUserStatus(User user) throws UserDisabledException, LoginFailureLimitException {
        if (!user.isActive()) {
            throw new UserDisabledException();
        }
        if(user.getStatus().equals(UserRoleOrgStatus.WAIT_AUDIT.getValue())){
            throw new UserNotEffectiveException();
        }else{
            if(user.getStatus().equals(UserRoleOrgStatus.INVALID.getValue())){
                //账号已停用
                throw new UserDisabledException();
            }else{
                //审核通过
                if(!user.getIsSys()){
                    //机构id判空
                    if(StringUtils.isNotBlank(user.getOrgId())){
                        Optional<Org> orgOptional = orgRepository.findById(user.getOrgId());
                        Org org = orgOptional.orElseThrow(OrgNotFoundException::new);
                        if(org.getStatus().equals(UserRoleOrgStatus.INVALID.getValue())){
                            //账号已停用
                            throw new UserDisabledException();
                        }
                    }
                    LocalDate effectiveDate = user.getEffectiveDate();
                    if(effectiveDate != null && effectiveDate.isAfter(LocalDate.now())){
                        throw new UserNotEffectiveException();
                    }
                    LocalDate expireDate = user.getExpireDate();
                    if(expireDate != null && expireDate.isBefore(LocalDate.now())){
                        throw new UserExpiredException();
                    }
                }
            }
        }
    }

    private void fillRoleCodeListAndResourceList(String appId,
                                                 UserInfoResult<?> userInfoResult, List<Role> roleList,
                                                 boolean withResources){
        if (roleList != null && !roleList.isEmpty()) {
            userInfoResult.setRoles(roleList.stream().map(Role::getRoleCode).collect(Collectors.toList()));
            if (!withResources) {
                return;
            }
            Set<String> resourceCodeList = new HashSet<>();
            roleList.forEach((r) -> {
                List<Resource> resources = findResourcesByRoleId(appId, r.getId());
                if (resources != null) {
                    resourceCodeList.addAll(resources.stream().map(resource -> resource.getCode()).collect(Collectors.toList()));
                }
            });
            if (resourceCodeList.isEmpty()) {
                return;
            }
            userInfoResult.setResourcesList(resourceCodeList);
        }
    }

//    private void fillRoleCodeListAndResourceList1(String appId,
//                                                 UserInfoResult<?> userInfoResult, List<Role> roleList,
//                                                 boolean withResources) {
//        if (roleList != null && !roleList.isEmpty()) {
//            userInfoResult.setRoles(roleList.stream().map(Role::getRoleCode).collect(Collectors.toList()));
//            if (!withResources) {
//                return;
//            }
//            Set<Resource> resourceList = new HashSet<>();
//            roleList.forEach((r) -> {
//                List<Resource> resources = findResourcesByRoleId(appId, r.getId());
//                if (resources != null) {
//                    resourceList.addAll(resources);
//                }
//            });
//            if (resourceList.isEmpty()) {
//                return;
//            }
//            List<Resource> prodMenuList = Tree.toTreeList("", resourceList.stream()
//                    .filter(r -> ResourceType.PRODUCT.name().equals(r.getType())
//                            || ResourceType.MENU.name().equals(r.getType())
//                            || ResourceType.PAGE.name().equals(r.getType()))
//                    .collect(Collectors.toList()));
//            if (prodMenuList.isEmpty()) {
//                return;
//            }
//            List<Resource> children = prodMenuList.get(0).getChildren();
//            Collections.sort(children);
//            children.forEach(resource -> {
//                if (resource.getChildren() != null) {
//                    Collections.sort(resource.getChildren());
//                }
//            });
//            userInfoResult.setMenuList(children);
//
//            List<Resource> pageList = resourceList.stream().filter(r -> ResourceType.PAGE.name().equals(r.getType()))
//                    .collect(Collectors.toList());
//
//            if (pageList.isEmpty()) {
//                return;
//            }
//            Map<String, List<String>> permissionMap = new HashMap<>(pageList.size());
//            pageList.forEach(p ->
//                    permissionMap.put(p.getName(), resourceList.stream()
//                            .filter(r -> ResourceType.BUTTON.name().equals(r.getType()) && p.getId().equals(r.getPid()))
//                            .map(Resource::getName)
//                            .collect(Collectors.toList()))
//            );
//            userInfoResult.setPermissionMap(permissionMap);
//        }
//    }

    private UserDetails buildUserDetailsByUser(User u) {
        SecurityUser securityUser = new SecurityUser(u.getId(), u.getMobile(), u.getMobile(), u.getPassword(),
                u.getMaxInactiveInterval());
        securityUser.setActive(u.isActive());
        return securityUser;
    }

    private List<Role> findRolesByUserId(String userId) {
        List<UserRole> userRoleList = userRoleRepository.findAllByUserId(userId);
        if (userRoleList != null && !userRoleList.isEmpty()) {
            return roleRepository.findAllByIdIn(
                    userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toList()))
                    .stream().filter(role -> role.getStatus().equals(UserRoleOrgStatus.VALID.getValue())).collect(Collectors.toList());
        }
        return null;
    }

    private List<Resource> findResourcesByRoleId(String appId, String roleId) {
        List<RoleResource> roleResourceList = roleResourceRepository.findAllByRoleId(roleId);
        if (roleResourceList != null && !roleResourceList.isEmpty()) {
            return resourceRepository.findAllByAppIdAndIdInOrderBySortAsc(appId,
                    roleResourceList.stream().map(RoleResource::getResourceId).collect(Collectors.toList()));
        }
        return null;
    }
}
